<template>
  <div class="page page-img-filter">
    <h4 class="text-center">图片批量滤镜处理</h4>
    <form class="page-body row">
      <div class="col-2">
        <fieldset>
          <legend>效果列表</legend>
          <div class="fieldset-content">
            <div class="form-group effects-list">
              <div v-for="(v,i) in effectsList" :key="i"
                :class="['effect-item', {active: i === effectActiveIndex}]"
                @click="effectItemClick(i)"
              >
                <img :src="v.imgSrc">
                <i v-if="i>0" class="fa fa-close" @click.stop="effectItemDelete(i)" title="删除"></i>
                <div class="label">{{v.label}}</div>
              </div>
            </div>
            <div class="form-group">
              <x-button type="primary" block @click="importLutCube" >导入LutCube</x-button>
            </div>
          </div>
        </fieldset>
      </div>
      <div class="col-10">
        <fieldset>
          <legend>操作区域</legend>
          <div class="fieldset-content">
            <div class="form-group preview">
              <img v-if="imageActive.canvasImgSrc" :src="imageActive.canvasImgSrc" />
              <div v-else class="nodata">图片预览</div>
              <i v-if="imageActive.canvasImgSrc" class="fa fa-download" @click="downloadActive" title="下载"></i>
            </div>
            <div class="form-group images-list-wrapper" @drop.prevent="drop" @dragover.prevent>
                <div class="images-list">
                    <div class="images-list-inner">
                        <div v-for="(v,i) in imagesList" :key="i"
                            :class="['image-item', {active: i === imageActiveIndex}]"
                            @click="imageItemClick(i)"
                        >
                            <img :src="v.src">
                            <i class="fa fa-close" @click.stop="imgItemDelete(i)" title="删除"></i>
                        </div>
                    </div>
                </div>
                <div v-show="imagesList.length===0" class="nodata">将待处理图片拖动到此处</div>
            </div>
            <div class="form-group">
              <x-button type="primary" block @click="submitForm" :loading="loadingCount > 0">批量处理&导出</x-button>
            </div>
          </div>
        </fieldset>
      </div>
      <div v-if="loadingCount > 0" class="mask">
        <x-loading></x-loading>
      </div>
    </form>
  </div>
</template>

<script>
const path = global.require('path')
const fs = global.require('fs')
const {remote} = global.require('electron')
const {dialog} = remote
const selfWindow= remote.getCurrentWindow()

export default {
  name: 'page-img-filter',
  data () {
    return {
      toPath: this.$sharedObject('desktopPath'),
      exampleImg: this.loadFile('img.jpg'),
      exampleImgObj: null,
      exampleCanvas: null,
      exampleCtx: null,
      effectsList: [
        {label: '原图', filter: 'original', imgSrc: this.loadFile('img.jpg')},
        // {label: '美肤效果', filter: 'softenFace', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '素描效果', filter: 'sketch', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '自然增强', filter: 'softEnhancement', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '紫调效果', filter: 'purpleStyle', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '柔焦效果', filter: 'soften', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '复古效果', filter: 'vintage', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '黑白效果', filter: 'gray', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '仿lomo', filter: 'lomo', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '亮白增强', filter: 'strongEnhancement', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '灰白效果', filter: 'strongGray', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '灰色效果', filter: 'lightGray', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '暖秋效果', filter: 'warmAutumn', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '木雕效果', filter: 'carveStyle', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
        // {label: '粗糙效果', filter: 'rough', imgSrc: 'this.loadFile('img-3dlut.jpg')'},
      ],
      effectActiveIndex: 0,
      imagesList: [],
      imageActiveIndex: -1,
      loadingCount: 0
    }
  },
  computed: {
      effectActive () {
        return this.effectsList[this.effectActiveIndex] || {}
      },
      imageActive () {
        return this.imagesList[this.imageActiveIndex] || {}
      }
  },
  mounted () {
    this.initExampleImg()
  },
  methods: {
    // 初始化示例图片信息
    initExampleImg () {
      let demoItem = this.effectsList[0]
      let originWidth = 0
      let originHeight = 0
      return this.$jstools.loadImage(demoItem.imgSrc).then((imgObj) => {
        this.exampleImgObj = imgObj
        originWidth = imgObj.width
        originHeight = imgObj.height
      }).then(() => {
        let canvas = document.createElement('canvas')
        let ctx = canvas.getContext('2d')
        canvas.width = originWidth
        canvas.height = originHeight
        this.exampleCanvas = canvas
        this.exampleCtx = ctx
      })
    },
    // 导入LUT CUBE
    importLutCube () {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择文件',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openFile', 'multiSelections']
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this.loadLutCubeList(filePaths)
      }
    },
    loadLutCubeList (filePaths) {
      let taskList = []
      filePaths.forEach(filePath => {
        taskList.push(
          this.loadLutCube(filePath)
        )
      })
      this.loadingCount++
      Promise.all(taskList).finally(() => {
        this.$toast('导入完成')
        this.loadingCount--
      })
    },
    loadLutCube (filePath) {
      if (!filePath) return Promise.reject()
      return new Promise((resolve, reject) => {
        let buf = fs.readFileSync(filePath)
        let lut = this.$sharedObject('parseCubeLUT')(buf)
        if (!lut) reject()
        let filename = path.basename(filePath)
        let label = lut.title || filename || 'noname'
        let promiseObj = Promise.resolve()
        if (!this.exampleCtx) {
          promiseObj = this.initExampleImg()
        }
        promiseObj.then(() => {
          this.exampleCtx.clearRect(0, 0, this.exampleCanvas.width, this.exampleCanvas.height)
          this.exampleCtx.drawImage(this.exampleImgObj, 0, 0)
          let imageData = this.exampleCtx.getImageData(0, 0, this.exampleCanvas.width, this.exampleCanvas.height)
          let imageDataNew = new ImageData(this.filterItemData(imageData.data, lut.data), imageData.width, imageData.height)
          this.exampleCtx.clearRect(0, 0, this.exampleCanvas.width, this.exampleCanvas.height)
          this.exampleCtx.putImageData(imageDataNew, 0, 0)
          this.effectsList.push({
            label,
            filter: `${Math.random()}`.substring(2) + `_${Date.now()}`,
            imgSrc: this.exampleCanvas.toDataURL('image/png'),
            data: lut.data
          })
          resolve()
        })
      })
    },
    // 拖拽添加图片
    drop (e) {
      const fileList = e.dataTransfer.files
      if (!fileList) return
      // 文件类型不存在即认为是文件夹或特殊文件
      // 取文件后缀名，必须是图片文件
      let taskList = []
      let indexList = []
      for (let i = 0; i < fileList.length; i++) {
        const file = fileList[i]
        if (!file.type) {
          return this.$toast('不支持文件夹及特殊格式文件！')
        }
        if (!/.(gif|jpg|jpeg|png|gif|jpg|png)$/i.test(file.name)) {
          return this.$toast('请选择图片文件！')
        }
        taskList.push(this.$jstools.File2dataUrl(file))
        indexList.push(i)
      }
      Promise.all(taskList).then((urls) => {
        urls.forEach((v, i) => {
          let file = fileList[indexList[i]]
          this.imagesList.push({
            name: file.name, src: v, path: file.path, mimeType: file.type,
            originWidth: 0, originHeight: 0, imgObj: null,
            canvas: null, canvasImgSrc: '', filter: 'original',
          })
        })
        this.loadImage()
      })
    },
    // 加载原始图片
    loadImage () {
      this.imagesList.forEach((v, i) => {
        if (v.imgObj) return
        this.$jstools.loadImage(v.src).then((imgObj) => {
          this.imagesList[i].imgObj = imgObj
          this.imagesList[i].originWidth = imgObj.width
          this.imagesList[i].originHeight = imgObj.height
        })
      })
    },
    // 删除图片
    imgItemDelete (index) {
      this.imagesList.splice(index, 1)
      if (this.imageActiveIndex === index) {
        this.imageActiveIndex = -1
      } else if (this.imageActiveIndex > index) {
        this.imageActiveIndex -= 1
      }
    },
    // 删除效果
    effectItemDelete (index) {
      this.effectsList.splice(index, 1)
      if (this.effectActiveIndex === index) {
        this.effectActiveIndex = 0
      } else if (this.effectActiveIndex > index) {
        this.effectActiveIndex -= 1
      }
    },
    // 效果Item被点击
    effectItemClick (index) {
      if (this.effectActiveIndex === index) return
      this.effectActiveIndex = index
      this.imagesList = this.imagesList.map(v => {
        v.filter = 'original'
        v.canvas = null
        v.canvasImgSrc = ''
        return v
      })
      if (this.imageActiveIndex > -1) {
        this.loadingCount++
        setTimeout(() => {
          this.filterItem(this.imageActiveIndex).finally(() => {
            this.loadingCount--
          })
        }, 0)
      }
    },
    // 图片Item被点击
    imageItemClick (index) {
      if (this.imageActiveIndex === index) return
      this.imageActiveIndex = index
      this.loadingCount++
      setTimeout(() => {
        this.filterItem(index).finally(() => {
          this.loadingCount--
        })
      }, 0)
    },
    // 下载当前图片
    downloadActive () {
      if (!this.imageActive.canvas) return
      this.imageActive.canvas.toBlob((blob) => {
        if (blob) {
          let fp = path.join(this.toPath, this.imageActive.name)
          this.saveBlob(blob, this.imageActive.name).then(() => {
            this.$toast(`下载完成，图片保存在 ${fp}`)
          })
        }
      })
    },
    // 单张处理
    filterItem (index) {
      return new Promise((resolve, reject) => {
        let imageItem = this.imagesList[index] || {}
        let {originWidth, originHeight} = imageItem
        if (!originWidth || !originHeight) {
          this.$toast('图片信息错误')
          reject('图片信息错误')
        } else if (imageItem.canvasImgSrc && (imageItem.filter === this.effectActive.filter)) {
          resolve(imageItem)
        } else {
          let canvas = document.createElement('canvas')
          let ctx = canvas.getContext('2d')
          canvas.width = originWidth
          canvas.height = originHeight
          ctx.clearRect(0, 0, originWidth, originHeight)
          ctx.drawImage(imageItem.imgObj, 0, 0)
          let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
          let imageDataNew = new ImageData(this.filterItemData(imageData.data, this.effectActive.data), imageData.width, imageData.height)
          ctx.putImageData(imageDataNew, 0, 0)
          imageItem.canvas = canvas
          imageItem.filter = this.effectActive.filter
          imageItem.canvasImgSrc = canvas.toDataURL('image/png')
          this.imagesList[index] = imageItem
          resolve(imageItem)
        }
      })
    },
    // 单张处理 - 修改数据
    filterItemData (data, LUTDataPoints) {
      for (let i = data.length - 4; i > -1; i -= 4) {
        let color = this.transColorBy3dlut(data[i + 0], data[i + 1], data[i + 2], LUTDataPoints)
        data[i + 0] = color[0]
        data[i + 1] = color[1]
        data[i + 2] = color[2]
      }
      return data
    },
    // 单张处理 - 色值转换
    transColorBy3dlut (r, g, b, LUTDataPoints) {
      let result = [r, g, b]
      LUTDataPoints = LUTDataPoints || this.effectActive.data
      if (LUTDataPoints) {
        let _r = Math.floor(Math.cbrt(LUTDataPoints.length))
        let rgbArr = [r, g, b]
        rgbArr = rgbArr.map(v => {
          let _v = Math.round((v / 255) * 16)
          return _v
        })
        let rowCount = rgbArr[0] + rgbArr[1] * _r + rgbArr[2] * _r * _r
        result = LUTDataPoints[rowCount].map(v => (v * 255))
      }
      return result
    },
    // todo: 更精细，目前特别消耗性能且有大幅偏差
    transColorCareful (r, g, b, LUTDataPoints) {
      let result = [r, g, b]
      // 数据整理
      let rgbArr = [r, g, b]
      let rgbDic = {r: [], g: [], b: []}
      let rgbKey = ['r', 'g', 'b']
      rgbArr = rgbArr.map((v, i) => {
        let _v = (v / 255) * 16
        rgbDic[rgbKey[i]] = [Math.floor(_v), Math.ceil(_v)]
        return _v
      })
      // 相关 3d lut 点计算
      let pointsDic = {}
      let pointsArr = [{}]
      let _index = 0
			while (rgbKey[_index]) {
				let key = rgbKey[_index]
				let _pointsArr = []
				rgbDic[key].forEach((v, i) => {
					pointsArr.forEach(item => {
						let _item = this.$sharedObject('jstoolsNode').deepCopy(item)
						_item[key] = v
						_item['key'] = (_item['key'] ? `${_item['key']}_${key}${i}` : `${key}${i}`)
						_pointsArr.push(_item)
					})
				})
				pointsArr = _pointsArr
				_index++
			}
      pointsArr = pointsArr.map(v => {
        let row = v.r + v.g * 17 + v.b * 17 * 17
        v.lut = LUTDataPoints[row].map(v => (v * 255))
        pointsDic[v.key] = v
        return v
      })
      // 根据相关色点计算结果
      let rDic = {} // rgb比率字典
      rgbArr.forEach((v, i) => {
        let key = rgbKey[i]
        let v1 = `${v}`.split('.')[1] || 0
        let v0 = 1 - v1
        rDic[`${key}0`] = v0
        rDic[`${key}1`] = v1
      })
      let p1 = pointsDic['r0_g0_b0'].lut
      let p2 = pointsDic['r0_g0_b1'].lut
      let p3 = pointsDic['r0_g1_b0'].lut
      let p4 = pointsDic['r0_g1_b1'].lut
      let p5 = pointsDic['r1_g0_b0'].lut
      let p6 = pointsDic['r1_g0_b1'].lut
      let p7 = pointsDic['r1_g1_b0'].lut
      let p8 = pointsDic['r1_g1_b1'].lut
      result = result.map((v, i) => {
        v = ((p1[i] * rDic.b0 +  p2[i] * rDic.b1) * rDic.g0 + (p3[i] * rDic.b0 +  p4[i] * rDic.b1) * rDic.g1) * rDic.r0 + ((p5[i] * rDic.b0 +  p6[i] * rDic.b1) * rDic.g0 + (p7[i] * rDic.b0 +  p8[i] * rDic.b1) * rDic.g1) * rDic.r1
        v *= 255
        return v
      })
      return result
    },
    // 将Blob数据写到本地磁盘
    saveBlob (blob, filename) {
      if (!blob || !filename) return Promise.reject()
      return blob.arrayBuffer().then((res) => {
        let fp = path.join(this.toPath, filename)
        this.$sharedObject('jstoolsNode').writeFile(fp, Buffer.from(res))
      }).catch((err) => {
        this.$toast('发生错误：' + err)
      })
    },
    // 批量处理&导出
    submitForm () {
      let taskList = []
      this.imagesList.forEach((v, i) => {
        taskList.push(
          this.filterItem(i).then((imageItem) => {
            if (!imageItem.canvas) return
            imageItem.canvas.toBlob((blob) => {
              if (blob) {
                this.saveBlob(blob, imageItem.name)
              }
            })
          })
        )
      })
      this.loadingCount++
      Promise.all(taskList).finally(() => {
        this.$toast('操作已完成')
        this.loadingCount--
      })
    },
    loadFile (filename) {
      return require(`@/assets/images/public/${filename}`)
    },
  }
}
</script>

<style scoped lang="less">
@import "~@/assets/css/_mixins.less";
@import "~@/assets/css/_color.less";
@gapSize: 10px;

.page-img-filter {
  display: flex;
  flex-direction: column;
  .page-body {
    height: 0;
    flex-grow: 1;
    position: relative;
    fieldset {
      height: 100%;
      .fieldset-content {
        height: calc(100% - 2em);
        display: flex;
        flex-direction: column;
      }
      .effects-list {
        height: 0;
        flex-grow: 1;
        border-radius: 6px;
        background-color: rgba(0,0,0,.1);
        overflow-y: auto;
        .effect-item {
          margin: @gapSize;
          background-color: rgba(0,0,0,.1);
          border-radius: 6px;
          border-width: 4px;
          border-style: solid;
          border-color: transparent;
          overflow: hidden;
          position: relative;
          img {
            display: block;
            width: 100%;
            height: 6vw;
            object-fit: cover;
          }
          .label {
            position: absolute;
            left: 0;
            right: 0;
            bottom: 0;
            padding: .25em;
            font-size: 1vw;
            color: #fff;
            text-align: center;
            letter-spacing: .1em;
            background-color: rgba(0,0,0,.5);
          }
          &.active {
            border-color: #fff;
          }
          .fa-close {
            position: absolute;
            top: 0;
            right: 0;
            z-index: 2;
            padding: .15em 0.5em;
            font-size: 1vw;
            color: #fff;
            border-radius: 0 0 0 .3em e('/') 0 0 0 .3em;
            background-color: @colorPrimary;
            cursor: pointer;
          }
        }
      }
      .preview {
        height: 0;
        flex-grow: 1;
        border-radius: 6px;
        background-color: rgba(0,0,0,.1);
        overflow: hidden;
        position: relative;
        img {
          .absolute-fullscreen;
          width: 100%;
          height: 100%;
          object-fit: contain;
        }
        .fa-download {
          position: absolute;
          top: 0;
          right: 0;
          z-index: 2;
          padding: .5em 1em;
          font-size: 1vw;
          color: #fff;
          border-radius: 0 0 0 .3em e('/') 0 0 0 .3em;
          background-color: @colorPrimary;
          cursor: pointer;
        }
      }
      .images-list-wrapper {
        height: 150px;
        border-radius: 6px;
        background-color: rgba(0,0,0,.1);
        position: relative;
        .images-list {
          position: absolute;
          left: @gapSize;
          right: @gapSize;
          top: @gapSize;
          bottom: @gapSize;
          overflow: auto hidden;
        }
        .images-list-inner {
          display: flex;
          flex-wrap: nowrap;
        }
        .image-item {
          border-width: 2px;
          border-style: solid;
          border-color: transparent;
          border-radius: .3em;
          background-color: rgba(255,255,255,.3);
          overflow: hidden;
          position: relative;
          img {
            display: inline-block;
            height: 130px;
            width: auto;
          }
          .fa-close {
            position: absolute;
            top: 0;
            right: 0;
            z-index: 2;
            padding: .15em 0.5em;
            font-size: 1vw;
            color: #fff;
            border-radius: 0 0 0 .3em e('/') 0 0 0 .3em;
            background-color: @colorPrimary;
            cursor: pointer;
          }
          &.active {
            border-color: @colorWarning;
          }
          &:not(:last-child) {
            margin-right: @gapSize;
          }
        }
      }
    }
    .mask {
      position: absolute;
      left: 15px;
      right: 15px;
      top: 0;
      bottom: 0;
      border-radius: 8px;
      background-color: rgba(255,255,255,.5);
      .flex-center;
    }
  }
}
</style>
